﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;

namespace Sharp.Portal.WebAPI
{
    /// <summary>
    /// 多参数绑定
    /// </summary>
    public class MultipleBodyParametersBinding : HttpParameterBinding
    {

        private const string MultipleBodyParameters = "MultipleBodyParameters";
        /// <summary>
        /// 多参数绑定
        /// </summary>
        /// <param name="descriptor"></param>
        public MultipleBodyParametersBinding(HttpParameterDescriptor descriptor) : base(descriptor) { }

        /// <summary>
        /// Check for simple binding parameters in POST data. Bind POST
        /// data as well as query string data
        /// </summary>
        /// <param name="metadataProvider"></param>
        /// <param name="actionContext"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            string stringValue = null;

            try
            {

                NameValueCollection col = TryReadBody(actionContext.Request);

                if (col != null)
                    stringValue = col[Descriptor.ParameterName];

                // try reading query string if we have no POST/PUT match
                if (stringValue == null)
                {
                    var query = actionContext.Request.GetQueryNameValuePairs();

                    if (query != null)
                    {
                        var matches = query.Where(kv => kv.Key.ToLower() == Descriptor.ParameterName.ToLower());

                        if (matches.Count() > 0)
                            stringValue = matches.First().Value;
                    }
                }

                object value = StringToType(stringValue);

                // Set the binding result here 给字段挨个赋值
                SetValue(actionContext, value);

                // now, we can return a completed task with no result
                TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>();

                tcs.SetResult(default(AsyncVoid));

                return tcs.Task;
            }

            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Method that implements parameter binding hookup to the global configuration object's
        /// ParameterBindingRules collection delegate.
        ///
        /// This routine filters based on POST/PUT method status and simple parameter
        /// types.
        /// </summary>
        /// <example>
        /// GlobalConfiguration.Configuration.
        /// .ParameterBindingRules
        /// .Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding);
        /// </example>
        /// <param name="descriptor"></param>
        /// <returns></returns>
        public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor)
        {
            try
            {
                var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods;

                // Only apply this binder on POST operations
                if (supportedMethods.Contains(HttpMethod.Post))
                {
                    var supportedTypes = new Type[] {
                    typeof(string),
                    typeof(int),
                    typeof(long),
                    typeof(long ? ),
                    typeof(decimal),
                    typeof(double),
                    typeof(bool),
                    typeof(DateTime),
                    typeof(byte[])
                };

                    if (supportedTypes.Where(typ => typ == descriptor.ParameterType).Count() > 0)
                        return new MultipleBodyParametersBinding(descriptor);
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }

            return null;
        }

        private object StringToType(string stringValue)
        {

            object value = null;

            try
            {
                if (stringValue == null)
                    value = null;
                else if (Descriptor.ParameterType == typeof(string))
                    value = stringValue;
                else if (Descriptor.ParameterType == typeof(int))
                    value = int.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(Int32))
                    value = Int32.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(Int64))
                    value = Int64.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(decimal))
                    value = decimal.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(double))
                    value = double.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(DateTime))
                    value = DateTime.Parse(stringValue, CultureInfo.CurrentCulture);
                else if (Descriptor.ParameterType == typeof(bool))
                {
                    value = false;

                    if (stringValue == "true" || stringValue == "on" || stringValue == "1")
                        value = true;
                }
                else
                    value = stringValue;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return value;
        }

        /// <summary>
        /// Read and cache the request body
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private NameValueCollection TryReadBody(HttpRequestMessage request)
        {
            object result = null;

            try
            {
                if (!request.Properties.TryGetValue(MultipleBodyParameters, out result))
                {
                    var contentType = request.Content.Headers.ContentType.MediaType.ToLower();

                    if (contentType == null)
                    {
                        result = null;
                    }
                    else if (contentType.Contains("application/x-www-form-urlencoded"))
                    {
                        result = request.Content.ReadAsFormDataAsync().Result;
                    }
                    else if (contentType.Contains("application/json")) //解决json问题
                    {
                        var jsonStr = request.Content.ReadAsStringAsync().Result; //{"Name":"tongl","Age":22}
                        var json = JsonConvert.DeserializeObject<IDictionary<string,
                        string>>(jsonStr);

                        if (json != null || json.Count > 0)
                        {
                            var nvc = new NameValueCollection();
                            foreach (var item in json)
                            {
                                nvc.Add(item.Key, item.Value);
                            }
                            result = nvc;
                        }
                    }
                    else
                    {
                        result = null;
                    }

                    request.Properties.Add(MultipleBodyParameters, result);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return result as NameValueCollection;
        }

        private struct AsyncVoid
        {

        }
    }
}
